#   Copyright (C) 2008      Tri Tech Information Systems Inc.
# 
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
# 
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
# 
#   You should have received a copy of the GNU General Public License along
#   with this program; if not, write to the Free Software Foundation, Inc.,
#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#   
from pyxx.formatters import *
from pyxx.klass import ClassWrapper
from pyxx.parser import CodeModel

def inheritable(meth, child_names):
    if not meth.isVirtual:
        return False

    if meth.name in child_names:
        return False

    return True

class InterfaceWrapper( ClassWrapper ):
    CLASS_TEMPLATE = '''
    boost::python::object hold;
    release_func m_release;

    void __init_wrapper()
    {
        typedef %(holder)s Ptr;
        Ptr x(this);
        typedef pointee<Ptr>::type value_type;
        typedef objects::pointer_holder_back_reference<Ptr,value_type> holder;
        typedef objects::instance<holder> instance_t;

         void* memory = holder::allocate(this->pyself(), offsetof(instance_t, storage), sizeof(holder));
         try {
             (new (memory) holder(x))->install(this->pyself());

         }
         catch(...) {
             holder::deallocate(this->pyself(), memory);
             throw;
         }

         %(init_code)s

    }
    virtual bool owned()
    {
        return hold;
    }

    virtual ~Wrap_%(name)s()
    {
        if( hold && m_release)
        {
            m_release(this);
        }
   }

    void take_ownership(release_func release)
    {
        assert(!hold);
        hold = self();
        m_release = release;
    }



    PyObject * pyself() const
    {
        return bpy::detail::wrapper_base_::get_owner(*this); 
    }
    bpy::object self() const
    {
        return bpy::object( bpy::handle<>( bpy::borrowed( pyself() ) ) );
    }

    %(overloads)s

    '''
    WRAPPER_TEMPLATE = '''
        scope newclass = class_< %(templateargs)s >( %(args)s )
            %(methodlist)s
                ;
        expose_wrapper< %(holder)s, Wrap_%(name)s >();                
    '''                

    def __init__( self, ctx, klass, module, holder = None, no_init = None, python_name = None):

            if holder is None:
                holder = "boost::shared_ptr< %s > " % ("::".join(klass.qualifiedName))

            self.init_code = ""
            ClassWrapper.__init__(self, ctx, klass, module, holder = holder, 
                    noncopyable = True, no_init = False, python_name = python_name,
                    is_interface = True)

    @generator            
    def generate_wrapper_bases(self):
        return ": public %(qualifiedName)s, public bpy::wrapper< %(qualifiedName)s >, public IWrapper"


    def addInitCode(self, code):
        self.init_code += code

    def check_method_access_policy(self, meth):
        """
        Interfaces allow invoking protected methods
        """
        return meth.accessPolicy != CodeModel.AccessPolicy.Private

    def abstract_methods(self):
        """
        Returns a set of all the methods which:
            are pure virtuals
            and have been filtered out
        """
        fns = set()

        # grab all the methods in my base class
        for base in self.wrapper_bases:
            fns.update( base.abstract_methods() )

        # any method exposed to python is safe
        for meth in self.methods:
            fns.discard(meth.name)

        # for excluded methods its trickier
        for meth in self.excludedMethods:
            if meth.isAbstract:
                # an excluded abstract method will prevent
                # a succesful subclassing
                fns.add( meth.name )
            else:
                # if its excluded, but present in impl
                # we are fine
                fns.discard( meth.name )

        return fns         
    def do_prepare(self):
        """
        Called just after running the pyx script

        Replace all virtual methods with VirtualMethodWrappers
        """
        ClassWrapper.do_prepare(self)

        method_names = set()

        for meth in self.methods + self.excludedMethods:
            method_names.add(meth.name)

        for wrapper_base in self.wrapper_bases:
            
            for meth in wrapper_base.methods:
                # determine whether or not we inherit this method
                if inheritable(meth, method_names):
                    if meth.isVirtual:
                        meth = meth.reparent(self)
                    self.methods.append( meth )


    def generate_wrapped_class(self):
        """
        In the case of an interface, we wrap the subclass we create
        """
        return "Wrap_" + self.name

    @generator
    @formatter( indent)
    def generate_overloads(self):
        txt = [ m.generate(m.TEMPLATE_OVERLOAD) for m in self.methods ]
        txt += [ "// Start Excluded methods" ]
#        txt += [ m.generate(m.TEMPLATE_OVERLOAD) for m in self.excludedMethods ]

        if self.has_default_constructor and not self.no_init:
            txt.append("""
                Wrap_%(name)s(PyObject * obj) 
                {
                    bpy::detail::initialize_wrapper(obj, this);
                    __init_wrapper();
                }
                
                static void __construct(PyObject * pyobj)
                {
                    new Wrap_%(name)s(pyobj);
                }

                """)

        return txt            

    def add_default_constructor(self):
        return ['.def("__init__", Wrap_%(name)s::__construct )']


